<html><head><meta charset="utf-8"/><title>事件</title></head><body><h1>事件</h1><div class="x-wiki-content x-main-content">
 <p>
  因为JavaScript在浏览器中以单线程模式运行，页面加载后，一旦页面上所有的JavaScript代码被执行完后，就只能依赖触发事件来执行JavaScript代码。
 </p>
 <p>
  浏览器在接收到用户的鼠标或键盘输入后，会自动在对应的DOM节点上触发相应的事件。如果该节点已经绑定了对应的JavaScript处理函数，该函数就会自动调用。
 </p>
 <p>
  由于不同的浏览器绑定事件的代码都不太一样，所以用jQuery来写代码，就屏蔽了不同浏览器的差异，我们总是编写相同的代码。
 </p>
 <p>
  举个例子，假设要在用户点击了超链接时弹出提示框，我们用jQuery这样绑定一个
  <code>
   click
  </code>
  事件：
 </p>
 <script>
  $(function () {
    $('#test-link').click(function () {
        alert('Hello!');
    });
});
 </script>
 <pre><code>/* HTML:
 *
 * &lt;a id="test-link" href="#0"&gt;点我试试&lt;/a&gt;
 *
 */

// 获取超链接的jQuery对象:
var a = $('#test-link');
a.on('click', function () {
    alert('Hello!');
});
</code></pre>
 <p>
  实测：
  <a href="#0" id="test-link">
   点我试试
  </a>
 </p>
 <p>
  <code>
   on
  </code>
  方法用来绑定一个事件，我们需要传入事件名称和对应的处理函数。
 </p>
 <p>
  另一种更简化的写法是直接调用
  <code>
   click()
  </code>
  方法：
 </p>
 <pre><code>a.click(function () {
    alert('Hello!');
});
</code></pre>
 <p>
  两者完全等价。我们通常用后面的写法。
 </p>
 <p>
  jQuery能够绑定的事件主要包括：
 </p>
 <h3>
  鼠标事件
 </h3>
 <p>
  click: 鼠标单击时触发；
dblclick：鼠标双击时触发；
mouseenter：鼠标进入时触发；
mouseleave：鼠标移出时触发；
mousemove：鼠标在DOM内部移动时触发；
hover：鼠标进入和退出时触发两个函数，相当于mouseenter加上mouseleave。
 </p>
 <h3>
  键盘事件
 </h3>
 <p>
  键盘事件仅作用在当前焦点的DOM上，通常是
  <code>
   &lt;input&gt;
  </code>
  和
  <code>
   &lt;textarea&gt;
  </code>
  。
 </p>
 <p>
  keydown：键盘按下时触发；
keyup：键盘松开时触发；
keypress：按一次键后触发。
 </p>
 <h3>
  其他事件
 </h3>
 <p>
  focus：当DOM获得焦点时触发；
blur：当DOM失去焦点时触发；
change：当
  <code>
   &lt;input&gt;
  </code>
  、
  <code>
   &lt;select&gt;
  </code>
  或
  <code>
   &lt;textarea&gt;
  </code>
  的内容改变时触发；
submit：当
  <code>
   &lt;form&gt;
  </code>
  提交时触发；
ready：当页面被载入并且DOM树完成初始化后触发。
 </p>
 <p>
  其中，
  <code>
   ready
  </code>
  仅作用于
  <code>
   document
  </code>
  对象。由于
  <code>
   ready
  </code>
  事件在DOM完成初始化后触发，且只触发一次，所以非常适合用来写其他的初始化代码。假设我们想给一个
  <code>
   &lt;form&gt;
  </code>
  表单绑定
  <code>
   submit
  </code>
  事件，下面的代码没有预期的效果：
 </p>
 <pre><code>&lt;html&gt;
&lt;head&gt;
    &lt;script&gt;
        // 代码有误:
        $('#testForm).on('submit', function () {
            alert('submit!');
        });
    &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;form id="testForm"&gt;
        ...
    &lt;/form&gt;
&lt;/body&gt;
</code></pre>
 <p>
  因为JavaScript在此执行的时候，
  <code>
   &lt;form&gt;
  </code>
  尚未载入浏览器，所以
  <code>
   $('#testForm)
  </code>
  返回
  <code>
   []
  </code>
  ，并没有绑定事件到任何DOM上。
 </p>
 <p>
  所以我们自己的初始化代码必须放到
  <code>
   document
  </code>
  对象的
  <code>
   ready
  </code>
  事件中，保证DOM已完成初始化：
 </p>
 <pre><code>&lt;html&gt;
&lt;head&gt;
    &lt;script&gt;
        $(document).on('ready', function () {
            $('#testForm).on('submit', function () {
                alert('submit!');
            });
        });
    &lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;form id="testForm"&gt;
        ...
    &lt;/form&gt;
&lt;/body&gt;
</code></pre>
 <p>
  这样写就没有问题了。因为相关代码会在DOM树初始化后再执行。
 </p>
 <p>
  由于
  <code>
   ready
  </code>
  事件使用非常普遍，所以可以这样简化：
 </p>
 <pre><code>$(document).ready(function () {
    // on('submit', function)也可以简化:
    $('#testForm).submit(function () {
        alert('submit!');
    });
});
</code></pre>
 <p>
  甚至还可以再简化为：
 </p>
 <pre><code>$(function () {
    // init...
});
</code></pre>
 <p>
  上面的这种写法最为常见。如果你遇到
  <code>
   $(function () {...})
  </code>
  的形式，牢记这是
  <code>
   document
  </code>
  对象的
  <code>
   ready
  </code>
  事件处理函数。
 </p>
 <p>
  完全可以反复绑定事件处理函数，它们会依次执行：
 </p>
 <pre><code>$(function () {
    console.log('init A...');
});
$(function () {
    console.log('init B...');
});
$(function () {
    console.log('init C...');
});
</code></pre>
 <h3>
  事件参数
 </h3>
 <p>
  有些事件，如
  <code>
   mousemove
  </code>
  和
  <code>
   keypress
  </code>
  ，我们需要获取鼠标位置和按键的值，否则监听这些事件就没什么意义了。所有事件都会传入
  <code>
   Event
  </code>
  对象作为参数，可以从
  <code>
   Event
  </code>
  对象上获取到更多的信息：
 </p>
 <pre><code>$(function () {
    $('#testMouseMoveDiv').mousemove(function (e) {
        $('#testMouseMoveSpan').text('pageX = ' + e.pageX + ', pageY = ' + e.pageY);
    });
});
</code></pre>
 <script>
  $(function () {
    $('#testMouseMoveDiv').mousemove(function (e) {
        $('#testMouseMoveSpan').text('pageX = ' + e.pageX + ', pageY = ' + e.pageY);
    });
});
 </script>
 <p>
  效果实测：
 </p>
 <p>
  mousemove:
  <span id="testMouseMoveSpan">
  </span>
 </p>
 <div id="testMouseMoveDiv" style="display: block; width: 300px; height: 120px; border: 1px solid #ccc;">
  在此区域移动鼠标试试
 </div>
 <h3>
  取消绑定
 </h3>
 <p>
  一个已被绑定的事件可以解除绑定，通过
  <code>
   off('click', function)
  </code>
  实现：
 </p>
 <pre><code>function hello() {
    alert('hello!');
}

a.click(hello); // 绑定事件

// 10秒钟后解除绑定:
setTimeout(function () {
    a.off('click', hello);
}, 10000);
</code></pre>
 <p>
  需要特别注意的是，下面这种写法是无效的：
 </p>
 <pre><code>// 绑定事件:
a.click(function () {
    alert('hello!');
});

// 解除绑定:
a.off('click', function () {
    alert('hello!');
});
</code></pre>
 <p>
  这是因为两个匿名函数虽然长得一模一样，但是它们是两个
  <em>
   不同
  </em>
  的函数对象，
  <code>
   off('click', function () {...})
  </code>
  无法移除已绑定的第一个匿名函数。
 </p>
 <p>
  为了实现移除效果，可以使用
  <code>
   off('click')
  </code>
  一次性移除已绑定的
  <code>
   click
  </code>
  事件的所有处理函数。
 </p>
 <p>
  同理，无参数调用
  <code>
   off()
  </code>
  一次性移除已绑定的所有类型的事件处理函数。
 </p>
 <h3>
  事件触发条件
 </h3>
 <p>
  一个需要注意的问题是，事件的触发总是由用户操作引发的。例如，我们监控文本框的内容改动：
 </p>
 <pre><code>var input = $('#test-input');
input.change(function () {
    console.log('changed...');
});
</code></pre>
 <p>
  当用户在文本框中输入时，就会触发
  <code>
   change
  </code>
  事件。但是，如果用JavaScript代码去改动文本框的值，将***不会***触发
  <code>
   change
  </code>
  事件：
 </p>
 <pre><code>var input = $('#test-input');
input.val('change it!'); // 无法触发change事件
</code></pre>
 <p>
  有些时候，我们希望用代码触发
  <code>
   change
  </code>
  事件，可以直接调用无参数的
  <code>
   change()
  </code>
  方法来触发该事件：
 </p>
 <pre><code>var input = $('#test-input');
input.val('change it!');
input.change(); // 触发change事件
</code></pre>
 <p>
  <code>
   input.change()
  </code>
  相当于
  <code>
   input.trigger('change')
  </code>
  ，它是
  <code>
   trigger()
  </code>
  方法的简写。
 </p>
 <p>
  为什么我们希望手动触发一个事件呢？如果不这么做，很多时候，我们就得写两份一模一样的代码。
 </p>
 <h3>
  浏览器安全限制
 </h3>
 <p>
  在浏览器中，有些JavaScript代码只有在用户触发下才能执行，例如，
  <code>
   window.open()
  </code>
  函数：
 </p>
 <pre><code>// 无法弹出新窗口，将被浏览器屏蔽:
$(function () {
    window.open('/');
});
</code></pre>
 <p>
  这些“敏感代码”只能由用户操作来触发：
 </p>
 <pre><code>var button1 = $('#testPopupButton1');
var button2 = $('#testPopupButton2');

function popupTestWindow() {
    window.open('/');
}

button1.click(function () {
    popupTestWindow();
});

button2.click(function () {
    // 不立刻执行popupTestWindow()，100毫秒后执行:
    setTimeout(popupTestWindow, 100);
});
</code></pre>
 <p>
  当用户点击
  <code>
   button1
  </code>
  时，
  <code>
   click
  </code>
  事件被触发，由于
  <code>
   popupTestWindow()
  </code>
  在
  <code>
   click
  </code>
  事件处理函数内执行，这是浏览器允许的，而
  <code>
   button2
  </code>
  的
  <code>
   click
  </code>
  事件并未立刻执行
  <code>
   popupTestWindow()
  </code>
  ，延迟执行的
  <code>
   popupTestWindow()
  </code>
  将被浏览器拦截。
 </p>
 <script>
  $(function () {
    var button1 = $('#testPopupButton1');
    var button2 = $('#testPopupButton2');

    function popupTestWindow() {
        window.open('?t=' + new Date().getTime(), 'popupWindow', 'width=400&height=300');
    }

    button1.click(function () {
        popupTestWindow();
    });

    button2.click(function () {
        setTimeout(popupTestWindow, 100);
    });
});
 </script>
 <p>
  效果实测：
 </p>
 <p>
  <button class="uk-button" id="testPopupButton1" type="button">
   Button 1
  </button>
  <button class="uk-button" id="testPopupButton2" type="button">
   Button 2
  </button>
 </p>
 <h3>
  练习
 </h3>
 <p>
  对如下的Form表单：
 </p>
 <pre><code>&lt;!-- HTML结构 --&gt;
&lt;form id="test-form" action="test"&gt;
    &lt;legend&gt;请选择想要学习的编程语言：&lt;/legend&gt;
    &lt;fieldset&gt;
        &lt;p&gt;&lt;label class="selectAll"&gt;&lt;input type="checkbox"&gt; &lt;span class="selectAll"&gt;全选&lt;/span&gt;&lt;span class="deselectAll"&gt;全不选&lt;/span&gt;&lt;/label&gt; &lt;a href="#0" class="invertSelect"&gt;反选&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;&lt;label&gt;&lt;input type="checkbox" name="lang" value="javascript"&gt; JavaScript&lt;/label&gt;&lt;/p&gt;
        &lt;p&gt;&lt;label&gt;&lt;input type="checkbox" name="lang" value="python"&gt; Python&lt;/label&gt;&lt;/p&gt;
        &lt;p&gt;&lt;label&gt;&lt;input type="checkbox" name="lang" value="ruby"&gt; Ruby&lt;/label&gt;&lt;/p&gt;
        &lt;p&gt;&lt;label&gt;&lt;input type="checkbox" name="lang" value="haskell"&gt; Haskell&lt;/label&gt;&lt;/p&gt;
        &lt;p&gt;&lt;label&gt;&lt;input type="checkbox" name="lang" value="scheme"&gt; Scheme&lt;/label&gt;&lt;/p&gt;
		&lt;p&gt;&lt;button type="submit"&gt;Submit&lt;/button&gt;&lt;/p&gt;
    &lt;/fieldset&gt;
&lt;/form&gt;
</code></pre>
 <p>
  绑定合适的事件处理函数，实现以下逻辑：
 </p>
 <p>
  当用户勾上“全选”时，自动选中所有语言，并把“全选”变成“全不选”；
 </p>
 <p>
  当用户去掉“全不选”时，自动不选中所有语言；
 </p>
 <p>
  当用户点击“反选”时，自动把所有语言状态反转（选中的变为未选，未选的变为选中）；
 </p>
 <p>
  当用户把所有语言都手动勾上时，“全选”被自动勾上，并变为“全不选”；
 </p>
 <p>
  当用户手动去掉选中至少一种语言时，“全不选”自动被去掉选中，并变为“全选”。
 </p>
 <pre><code class="language-x-javascript">'use strict';

var
    form = $('#test-form'),
    langs = form.find('[name=lang]'),
    selectAll = form.find('label.selectAll :checkbox'),
    selectAllLabel = form.find('label.selectAll span.selectAll'),
    deselectAllLabel = form.find('label.selectAll span.deselectAll'),
    invertSelect = form.find('a.invertSelect');

// 重置初始化状态:
form.find('*').show().off();
form.find(':checkbox').prop('checked', false).off();
deselectAllLabel.hide();
// 拦截form提交事件:
form.off().submit(function (e) {
    e.preventDefault();
    alert(form.serialize());
});
----
// TODO:绑定事件
----
// 测试:
console.log('请测试功能是否正常。');
</code></pre>
 <form action="test" id="test-form">
  <legend>
   请选择想要学习的编程语言：
  </legend>
  <fieldset>
   <p>
    <label class="selectAll">
     <input type="checkbox"/>
     <span class="selectAll">
      全选
     </span>
     <span class="deselectAll">
      全不选
     </span>
    </label>
    <a class="invertSelect" href="#0">
     反选
    </a>
   </p>
   <p>
    <label>
     <input name="lang" type="checkbox" value="javascript"/>
     JavaScript
    </label>
   </p>
   <p>
    <label>
     <input name="lang" type="checkbox" value="python"/>
     Python
    </label>
   </p>
   <p>
    <label>
     <input name="lang" type="checkbox" value="ruby"/>
     Ruby
    </label>
   </p>
   <p>
    <label>
     <input name="lang" type="checkbox" value="haskell"/>
     Haskell
    </label>
   </p>
   <p>
    <label>
     <input name="lang" type="checkbox" value="scheme"/>
     Scheme
    </label>
   </p>
   <p>
    <button type="submit">
     Submit
    </button>
   </p>
  </fieldset>
 </form>
</div>
</body></html>